package com.cskaoyan.javase.oop3._2inner._6lambda._0introduction;

/**
 * Lambda 表达式是 JDK8 的一个新特性，可以取代接口的匿名内部类，写出更优雅的Java 代码。
 *
 * 如果说匿名内部类实际上是局部内部类的更进一步，简化了局部内部类
 * 那么Lambda就是匿名内部类更进一步，语法上更简洁了，代码更优雅了，是高端的玩法，是人上人的玩法。
 *
 * 以上描述总体就说了三点：
 *      1.Lambda表达式仍然是局部内部类，是特殊的局部内部类，仍然定义在局部位置。
 *          而且局部内部类的注意事项，也一样存在。
 *          局部内部类当中访问作用域内部的局部变量,该局部变量是一个常量
 *      2.Lambda表达式在取代匿名内部类时，不是全部都取代
 *          而是取代接口的匿名内部类，而类的匿名内部类Lambda表达式是不能取代的。
 *          (当然普通类,以及抽象类用匿名内部类写它们的子类是非常少见的场景)
 *      3.Lambda表达式是匿名内部类的更进一步，
 *          Lambda表达式得到的也不是一个类，而是一个对象，并且是接口的子类对象。
 *
 * 以上总结,Lambda表达式是替代接口的匿名内部类,用来创建接口的子类对象.
 *
 * Lambda表达式使用的前提:
 * Lambda表达式表示创建接口的子类对象,并不是所有的接口都可以
 * Lambda表达式对接口有特殊的要求: 要求该接口有且仅有一个强制子类实现的抽象方法
 * 在java中,把这种接口称之为"功能接口(Functional Interface)"
 * 在语法上,为了检验一个接口是否是功能接口,用注解"@FunctionalInterface"标记某接口
 *      如果该接口是功能接口,语法不会报错,否则就会编译报错
 * 关于功能接口,思考以下问题:
 *      1.功能接口中就只有一个方法吗?
 *          不是,因为接口中还可以定义实现方法,而实现方法不需要子类强制重写.
 *      2.功能接口中就只有一个抽象方法吗?
 *          并不是
 *          实际上在Java中,某些父类中的抽象方法,并不会强转子类实现
 *          考虑Object类,Java中每个类都继承它,都继承它的成员方法
 *          (特殊情况)如果这个抽象方法
 *              可以用Object类当中的成员方法作为它的实现,那么该抽象方法不会强制子类实现
 *
 * 功能接口具备后,开始根据Lambda表达式的语法,创建功能接口的子类对象:
 * Lambda表达式语法:
 * (形参列表) -> {
 *      // 方法体
 * }
 * 解释:
 *      1."(形参列表)"是"照抄"功能接口中,那个强制子类实现的抽象方法的形参列表
 *          如果是空参抽象方法,这里就直接写"()"
 *
 *      2."->"是Lambda表达式的运算符,"由英文横杠和大于号组成",可以读作"goes to"
 *      3."{}"是功能接口中"那个强制子类实现的抽象方法"的重写方法体
 *          注意是方法体,不是类体
 *          在方法体中定义变量,当然是定义局部变量
 *          而且由于这里是方法体,不是类体,所以这个匿名子类是不可能新增自身独有成员的
 *     以上语法就可以解释一个问题了: 为什么Lambda表达式要求接口是功能接口呢?
 *     因为Lambda表达式的语法只能,也必须要重写一个抽象方法,没有办法重写两个或者不重写.
 *
 * 如果按照以上语法直接在局部位置定义Lambda表达式,会编译报错,为什么呢?
 * 直接写语法,Lambda表达式创建的子类对象是无法明确类型的(不知道创建的是谁的子类对象)
 * 而Java是强类型语言,这种情况是不可能发生.
 *
 * 这种情况下,就必须由程序员在代码中,做出一些的额外的代码,指示该Lambda表达式创建的对象的类型,这个过程就是"Lambda表达式的类型推断"
 *
 * Lambda表达式的类型推断,主要由以下方式:
 *      1.直接用父类引用指向该Lambda表达式,也就是父接口的引用指向子类对象
 *      2.(Lambda表达式的特殊语法,很少见使用,了解即可)使用一个类似于强制类型转换的语法,直接告诉编译器该Lambda表达式创建的是哪个接口的子类对象
 *      ((父接口名)(Lambda表达式)).调用方法;
 *
 *      从实际的使用来说,以上两种方式都是非常少见
 *      3.Lambda表达式在开发中的使用,绝大多数都会配合方法来使用,配合方法一起来完成类型推断
 *      a.借助方法的形参数据类型
 *      b接触方法的返回值类型
 *
 * 以上Lambda表达式的基本使用就完成了!
 *
 * @since 14:20
 * @author wuguidong@cskaoyan.onaliyun.com
 */
public class Demo {
    public static void main(String[] args) {
        // Lambda表达式仍然是特殊的局部内部类,所以它仍然定义在局部位置
        // 用Lambda表达式创建接口ITest的子类对象
        ITest it = () -> {
            // 这里是重写的方法,不是类体,匿名子类不可能新增自身独有成员
            System.out.println("我是Lambda表达式实现的ITest子类对象,我很高级!");
        };
        it.test();

        ((ITest) (() -> {
            System.out.println("hello world!");
        })).test();

        // 借助方法的形参数据类型来完成Lambda表达式的类型推断
        method(() -> {
            System.out.println("借助方法的形参数据类型来完成Lambda表达式的类型推断!");
        });

        // 借助方法的返回值数据类型来完成Lambda表达式的类型推断
        ITest it2 = method();
        it2.test();
    }

    public static void method(ITest it) {
        it.test();
    }

    public static ITest method() {
        // 借助方法的返回值数据类型来完成Lambda表达式的类型推断
        return () -> {
            System.out.println("借助方法的返回值数据类型来完成Lambda表达式的类型推断!");
        };
    }
}

// 实际开发中,大多数见到的功能接口都是只有一个方法,且是抽象方法
@FunctionalInterface
interface ITest {
    void test();
}

@FunctionalInterface
interface IC {
    void test();

    default void test2() {
    }

    static void test3() {
    }

    // 写一个抽象方法,该抽象方法可以用Object类当中的hashCode方法作为实现,于是不强制普通子类实现
    int hashCode();

    // 写一个抽象方法,该抽象方法可以用Object类当中equals方法作为实现,不会强制普通子类实现
    boolean equals(Object c);
}

class ICImpl implements IC {
    @Override
    public void test() {
        System.out.println("hello world!");
    }
}

// @FunctionalInterface
// interface IA{}
// @FunctionalInterface
// interface IB{
//     void test();
//     void test2();
// }